// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt
// Project developers.  See the top-level LICENSE file for dates and other
// details.  No copyright assignment is required to contribute to VisIt.

// ************************************************************************* //
//                            avtDenovoFileFormat.C                           //
// ************************************************************************* //

#include <avtDenovoFileFormat.h>

#include <string>

#include <vtkFloatArray.h>
#include <vtkRectilinearGrid.h>
#include <vtkStructuredGrid.h>
#include <vtkUnstructuredGrid.h>

#include <avtDatabaseMetaData.h>
#include <avtMaterial.h>

#include <Expression.h>
#include <InvalidFilesException.h>
#include <InvalidVariableException.h>
#include <visit-hdf5.h>

using namespace std;

static bool readStringAttr(hid_t dataset,
                           const string &attrName,
                           vector<string> &attrs)
{
    hid_t attrId = H5Aopen(dataset, attrName.c_str(), H5P_DEFAULT);
    if (attrId < 0) return false;

    hid_t attrSpace = H5Aget_space(attrId);
    hsize_t dims[8];
    int ndims = H5Sget_simple_extent_dims(attrSpace, dims, NULL);
    attrs.resize(dims[0]);

    herr_t status;
    hid_t attrType = H5Aget_type(attrId);
    if (H5Tis_variable_str(attrType))
    {
        vector<char *> strbuffs(dims[0]);
        status = H5Aread(attrId, attrType, strbuffs.data());
        if (status < 0) return false;

        vector<char*>::iterator it = strbuffs.begin();
        for (int i = 0; i < attrs.size(); i++)
        {
            string &str = attrs[i];
            char *buff = *it++;
            str = string(buff);
            std::free(buff);
        }
    }
    else
    {
        size_t len = H5Tget_size(attrType);
        vector<char> charBuff(len * dims[0]);
        H5Aread(attrId, attrType, charBuff.data());
        const char *srcPtr = charBuff.data();
        for (int i = 0; i < attrs.size(); i++)
        {
            string &str = attrs[i];
            str = string(srcPtr);
            srcPtr += len;
        }        
    }

    H5Aclose(attrId);
    H5Sclose(attrSpace);
    
    return true;
}


// ****************************************************************************
//  Method: avtDenovoFileFormat constructor
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Fri Oct 6 11:06:44 PDT 2017
//
// ****************************************************************************

avtDenovoFileFormat::avtDenovoFileFormat(const char *filename)
    : avtSTMDFileFormat(&filename, 1), fileLoaded(false)
{
    H5open();
    H5Eset_auto(H5E_DEFAULT, NULL, NULL);
}


// ****************************************************************************
//  Method: avtDenovoFileFormat::FreeUpResources
//
//  Purpose:
//      When VisIt is done focusing on a particular timestep, it asks that
//      timestep to free up any resources (memory, file descriptors) that
//      it has associated with it.  This method is the mechanism for doing
//      that.
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Fri Oct 6 11:06:44 PDT 2017
//
// ****************************************************************************

void
avtDenovoFileFormat::FreeUpResources(void)
{
    for (int i = 0; i < varMetaData.size(); i++)
    {
        const varInfo &var = varMetaData[i];
        H5Sclose(var.spaceId);
        H5Dclose(var.varId);
    }
    varMetaData.resize(0);

    for (int i = 0; i < coordMetaData.size(); i++)
    {
        const varInfo &coord = coordMetaData[i];
        H5Sclose(coord.spaceId);
        H5Dclose(coord.varId);
    }
    coordMetaData.resize(0);
    
    H5Dclose(fileId);
    fileId = -1;
    fileLoaded = false;
}

// ****************************************************************************
//  Method: avtDenovoFileFormat::LoadFile
//
//  Purpose:
//      Load file
//
//  Programmer: Dave Pugmire
//  Creation:   Fri Oct 6 11:06:44 PDT 2017
//
// ****************************************************************************

void
avtDenovoFileFormat::LoadFile()
{
    if (fileLoaded)
        return;

    coordMetaData.resize(0);
    varMetaData.resize(0);

    fileId = H5Fopen(GetFilename(), H5F_ACC_RDONLY, 0);
    if (fileId < 0)
        EXCEPTION1(InvalidFilesException, GetFilename());

    //Mesh metadata.
    coordMetaData.resize(0);

    //string coords[3] = {"/denovo/mesh_x", "/denovo/mesh_y", "/denovo/mesh_z"};
    string coords[3] = {"/denovo/mesh_z", "/denovo/mesh_y", "/denovo/mesh_x"};

    for (int i = 0; i < 3; i++)
        coordMetaData.push_back(varInfo(fileId, coords[i], GetFilename()));

    varMetaData.push_back(varInfo(fileId, "/denovo/flux", GetFilename()));
    varMetaData.push_back(varInfo(fileId, "/denovo/source", GetFilename()));
    varMetaData.push_back(varInfo(fileId, "/denovo/uncflux", GetFilename()));
    varMetaData.push_back(varInfo(fileId, "/denovo/matids", GetFilename()));

    //Material IDs.
    hid_t matId = H5Dopen(fileId, "/denovo/matids", H5P_DEFAULT);
    if (matId < 0) EXCEPTION1(InvalidFilesException, GetFilename());
    hid_t matIdS = H5Dget_space(matId);
    if (matIdS < 0) EXCEPTION1(InvalidFilesException, GetFilename());
    int numDims = H5Sget_simple_extent_ndims(matIdS);
    hsize_t dims[12];
    H5Sget_simple_extent_dims(matIdS, dims, NULL);

    //Mix table.
    hid_t mixId = H5Dopen(fileId, "/denovo/mixtable", H5P_DEFAULT);
    if (mixId < 0) EXCEPTION1(InvalidFilesException, GetFilename());
    hid_t mixIdS = H5Dget_space(mixId);
    if (mixIdS < 0) EXCEPTION1(InvalidFilesException, GetFilename());
    
    numDims = H5Sget_simple_extent_ndims(mixIdS);
    H5S_class_t classType = H5Sget_simple_extent_type(mixIdS);
    H5Sget_simple_extent_dims(mixIdS, dims, NULL);
    int mixTableSize = dims[0];

    mixTable.resize(mixTableSize);
    hid_t mtType = H5Tcreate(H5T_COMPOUND, sizeof(mixTableEntry));
    H5Tinsert(mtType, "row", 0, H5T_NATIVE_INT);
    H5Tinsert(mtType, "col", sizeof(int), H5T_NATIVE_INT);
    H5Tinsert(mtType, "val", 2*sizeof(int), H5T_NATIVE_DOUBLE);
    H5Dread(mixId, mtType, H5S_ALL, H5S_ALL, H5P_DEFAULT, &mixTable[0]);

    readStringAttr(mixId, "names", materialNames);
    readStringAttr(mixId, "colors", materialColors);    

#if 0
    for (int i = 0; i < mixTable.size(); i++)
        cout<<i<<": "<<mixTable[i].row<<" "<<mixTable[i].col<<" "<<mixTable[i].val<<endl;
#endif

    fileLoaded = true;
}


// ****************************************************************************
//  Method: avtDenovoFileFormat::PopulateDatabaseMetaData
//
//  Purpose:
//      This database meta-data object is like a table of contents for the
//      file.  By populating it, you are telling the rest of VisIt what
//      information it can request from you.
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Fri Oct 6 11:06:44 PDT 2017
//
// ****************************************************************************

void
avtDenovoFileFormat::PopulateDatabaseMetaData(avtDatabaseMetaData *md)
{
    LoadFile();
    
    string meshName = "KBA_mesh";
    int numBlocks = 1;
    int blockOrigin = 0;
    int spatialDims = 3;
    int topologicalDims = 3;
    double *extents = NULL;
    AddMeshToMetaData(md, meshName, AVT_RECTILINEAR_MESH, extents, numBlocks, blockOrigin,
                      spatialDims, topologicalDims);

    for (int i = 0; i < varMetaData.size(); i++)
        for (int j = 0; j < varMetaData[i].varNames.size(); j++)
            AddScalarVarToMetaData(md, varMetaData[i].varNames[j], meshName, AVT_ZONECENT);

    //Add materials.
    if (materialNames.size() > 0)
    {
        avtMaterialMetaData *matMD = new avtMaterialMetaData;
        matMD->name = "materials";
        matMD->meshName = meshName;
        matMD->materialNames = materialNames;
        matMD->colorNames = materialColors;
        md->Add(matMD);
    }
}

// ****************************************************************************
//  Method: avtDenovoFileFormat::GetAuxiliaryData
//
//  Purpose:
//      Return auxiliary data.
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Fri Oct 6 11:06:44 PDT 2017
//
// ****************************************************************************

void *
avtDenovoFileFormat::GetAuxiliaryData(const char *var,
                                      int domain,
                                      const char *type,
                                      void *args, 
                                      DestructorFunction &df)
{
    void *ret = NULL;
    LoadFile();

    if (strcmp(type, AUXILIARY_DATA_MATERIAL) == 0)
    {
        ret = GetMaterial(var, domain);
        df = avtMaterial::Destruct;
        return ret;
    }
    
    return ret;
}


// ****************************************************************************
//  Method: avtDenovoFileFormat::GetMesh
//
//  Purpose:
//      Gets the mesh associated with this file.  The mesh is returned as a
//      derived type of vtkDataSet (ie vtkRectilinearGrid, vtkStructuredGrid,
//      vtkUnstructuredGrid, etc).
//
//  Arguments:
//      domain      The index of the domain.  If there are NDomains, this
//                  value is guaranteed to be between 0 and NDomains-1,
//                  regardless of block origin.
//      meshname    The name of the mesh of interest.  This can be ignored if
//                  there is only one mesh.
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Fri Oct 6 11:06:44 PDT 2017
//
// ****************************************************************************

vtkDataSet *
avtDenovoFileFormat::GetMesh(int domain, const char *meshname)
{
    LoadFile();
    
    vtkFloatArray *xyz[3] = {vtkFloatArray::New(), vtkFloatArray::New(), vtkFloatArray::New()};
    int dims[3];
    for (int i = 0; i < 3; i++)
    {
        dims[i] = coordMetaData[i].dims[0];
        xyz[i]->SetNumberOfTuples(dims[i]);
        H5Dread(coordMetaData[i].varId, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL, H5P_DEFAULT, xyz[i]->GetVoidPointer(0));
        H5Dclose(coordMetaData[i].varId);
    }
    
    vtkRectilinearGrid *grid = vtkRectilinearGrid::New();
    grid->SetDimensions(dims);
    grid->SetXCoordinates(xyz[0]);
    grid->SetYCoordinates(xyz[1]);
    grid->SetZCoordinates(xyz[2]);
    for (int i = 0; i < 3; i++)
        xyz[i]->Delete();
    return grid;
}


// ****************************************************************************
//  Method: avtDenovoFileFormat::GetVar
//
//  Purpose:
//      Gets a scalar variable associated with this file.  Although VTK has
//      support for many different types, the best bet is vtkFloatArray, since
//      that is supported everywhere through VisIt.
//
//  Arguments:
//      domain     The index of the domain.  If there are NDomains, this
//                 value is guaranteed to be between 0 and NDomains-1,
//                 regardless of block origin.
//      varname    The name of the variable requested.
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Fri Oct 6 11:06:44 PDT 2017
//
// ****************************************************************************

vtkDataArray *
avtDenovoFileFormat::GetVar(int domain, const char *varname)
{
    LoadFile();
    
    string varName(varname);

    hsize_t varIdx = -1;
    varInfo var;
    for (int i = 0; i < varMetaData.size(); i++)
        for (int j = 0; j < varMetaData[i].varNames.size(); j++)
        {
            if (varName == varMetaData[i].varNames[j])
            {
                var = varMetaData[i];
                varIdx = j;
                break;
            }
        }
    
    if (varIdx == -1)
        EXCEPTION1(InvalidVariableException, varname);

    hsize_t nx=0, ny=0, nz=0, num=0;
    int status=-1;
    vector<float> buff;
    
    if (var.dims.size() == 3)
    {
        nx = var.dims[0], ny = var.dims[1], nz = var.dims[2];
        num = nx*ny*nz;
        
        buff.resize(num);
        status = H5Dread(var.varId, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL, H5P_DEFAULT, &buff[0]);
    }
    else if (var.dims.size() == 4)
    {
        nx = var.dims[1], ny = var.dims[2], nz = var.dims[3];
        num = nx*ny*nz;

        hsize_t start[4] = {varIdx, 0,0,0};
        hsize_t count[4] = {1,nx,ny,nz};
        hsize_t stride[4] = {1,1,1,1};
        int r0 = H5Sselect_hyperslab(var.spaceId, H5S_SELECT_SET, start, stride, count, NULL);
    
        hid_t memId = H5Screate_simple(4, count, NULL);

        /*
          cout<<"DIMS: "<<var.dims[0]<<" "<<var.dims[1]<<" "<<var.dims[2]<<" "<<var.dims[3]<<endl;
          cout<<"ReadVar: "<<varName<<" spaceID= "<<var.spaceId<<endl;
          cout<<"s/c:  ["<<start[0]<<" "<<start[1]<<" "<<start[2]<<" "<<start[3]<<"]";
          cout<<"["<<count[0]<<" "<<count[1]<<" "<<count[2]<<" "<<count[3]<<"]";
          cout<<endl;
        */

        buff.resize(num);
        status = H5Dread(var.varId, H5T_NATIVE_FLOAT, memId, var.spaceId, H5P_DEFAULT, &buff[0]);
        H5Sclose(memId);
        //cout<<strerror(errno)<<endl;
    }
    
    if (status < 0)
        EXCEPTION1(InvalidVariableException, varname);
    
    vtkFloatArray *arr = vtkFloatArray::New();
    arr->SetNumberOfTuples(num);
        
    //Transpose array....
    for (int i = 0; i < nx; i++)
        for (int j = 0; j < ny; j++)
            for (int k = 0; k < nz; k++)
            {
                arr->SetTuple1(k*nx*ny + j*nx + i,
                               buff[k + j*nz + i*nz*ny]);
            }
    
    return arr;
}


// ****************************************************************************
//  Method: avtDenovoFileFormat::GetVectorVar
//
//  Purpose:
//      Gets a vector variable associated with this file.  Although VTK has
//      support for many different types, the best bet is vtkFloatArray, since
//      that is supported everywhere through VisIt.
//
//  Arguments:
//      domain     The index of the domain.  If there are NDomains, this
//                 value is guaranteed to be between 0 and NDomains-1,
//                 regardless of block origin.
//      varname    The name of the variable requested.
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Fri Oct 6 11:06:44 PDT 2017
//
// ****************************************************************************

vtkDataArray *
avtDenovoFileFormat::GetVectorVar(int domain, const char *varname)
{
    return NULL;
}

// ****************************************************************************
//  Method: avtDenovoFileFormat::GetMaterial
//
//  Purpose:
//      Return material info.
//
//  Programmer: pugmire -- generated by xml2avt
//  Creation:   Fri Oct 6 11:06:44 PDT 2017
//
// ****************************************************************************

avtMaterial *
avtDenovoFileFormat::GetMaterial(const std::string &var, int domain)
{
    int ndims = 3;
    int nx = coordMetaData[0].dims[0]-1;
    int ny = coordMetaData[1].dims[0]-1;
    int nz = coordMetaData[2].dims[0]-1;
    int dims[3] = {nz, ny, nx};

    int numMats = materialNames.size();
    vector<int> matNums(numMats);
    char **names = new char*[numMats];
    
    for (int i = 0; i < numMats; i++)
    {
        matNums[i] = i;
        names[i] = (char *)materialNames[i].c_str();
    }

    float **volFracs = new float*[numMats]();
    int nzones = nx*ny*nz;

    vtkDataArray *matid = GetVar(domain, "matids");
    for (int m = 0; m < numMats; m++)
    {
        volFracs[m] = new float[nzones];
        for (int i = 0; i < nzones; i++)
            volFracs[m][i] = 0.0f;
    }
    
    for (int i = 0; i < nzones; i++)
    {
        int row = (int)matid->GetTuple1(i);
        for (int j = 0; j < mixTable.size(); j++)
        {
            if (mixTable[j].row == row)
                volFracs[mixTable[j].col][i] = mixTable[j].val;
        }
    }

    matid->Delete();

    int majorOrder = 0;
    avtMaterial *mat = new avtMaterial(numMats, &matNums[0],
                                       names,
                                       ndims, dims,
                                       majorOrder,
                                       volFracs,
                                       NULL);

    for (int m = 0; m < numMats; m++)
        delete [] volFracs[m];
    delete [] volFracs;
    delete [] names;


    return mat;

#if 0

    vector<MatZoneMap> matMap;

    MatZoneMap mz;
    mz.name = "void";
    mz.matno = 0;
    mz.numClean = nzones;
    mz.cleanZones = new int[mz.numClean];
    for (int i = 0; i < mz.numClean; i++)
        mz.cleanZones[i] = i;

    mz.numMixed = 0;
    /*
    mz.numMixed = 2;
    mz.mixedZones = new int[mz.numMixed];
    mz.mixedZones[0] = 0;
    mz.mixedZones[0] = 1;
    mz.volFracs = new float[mz.numMixed];
    mz.volFracs[0] = .5;
    mz.volFracs[1] = .5;
    matMap.push_back(mz);
    */

    avtMaterial *mat = new avtMaterial(2, matnos, names,
                                       matMap, 3, dims, majorOrder);//, NULL);
#endif



#if 0

    //All must be >= 1 ??
    int matnos[NUM_MATS] = {1,2};
    int *matlist = new int[nzones];
    for (int i = 0; i < nzones; i++)
        matlist[i] = i % NUM_MATS + 1;

    vector<int> mixMat, mixZone, mixNext;
    vector<float> mixVF;

    //Build the mix table...
    for (int i = 0; i < mixTable.size(); i++)
    {
        if (mixTable[i].val < 1.0)
        {
            int matid = mixTable[i].row;
            int material = mixTale[i].col;
            
        }
    }
    /*
    int mixLen = 0;
    int *mixMat = NULL;
    int *mixZone = NULL;
    int *mixNext = NULL;
    float *mixVF = NULL;
    */

    //for (int i = 0; i < nzones; i++)
//        matlist[i] = -1;
    mixMat.push_back(1);
    mixZone.push_back(1);
    mixNext.push_back(1);
    mixVF.push_back(.5);
    mixVF.push_back(.5);
    

    avtMaterial *mat = new avtMaterial(nmats,
                                       matnos,
                                       names,
                                       ndims,
                                       dims,
                                       majorOrder,
                                       matlist,
                                       mixMat.size(),
                                       &mixMat[0],
                                       &mixNext[0],
                                       &mixZone[0],
                                       &mixVF[0],
                                       NULL);
    
    return mat;
#endif
}
